// udp_client.hpp
#pragma once
#include <sys/types.h>
#include <sys/socket.h>
#include <cstring>
#include <cerrno>
#include <iostream>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
using namespace std;
// 可以当做标识符常量使用
enum
{
    USAGE_ERR = 1, // 启动客户端错误
    SOCKET_ERR,    // 创建套接字失败
    BIND_ERR,      // 绑定失败
};

class Client
{
public:
    void Start(char *serverip, int serverport)
    {
        // 1.创建套接字
        // AF_INET表示是IPv4，sock_DGRAM表示无连接服务，UDP使用
        _socket = socket(AF_INET, SOCK_DGRAM, 0);
        if (_socket < 0)
        {
            cout << strerror(errno) << endl;
            exit(SOCKET_ERR);
        }
        // cout<<serverip<<" "<<serverport<<endl;

        //  client 这里要不要bind呢？要的！socket通信的本质[clientip:clientport, serverip:serverport]
        //  要不要自己bind呢？不需要自己bind，也不要自己bind，OS自动给我们进行bind -- 为什么？client的port要随机让OS分配防止client出现启动冲突
        //  server 为什么要自己bind？1. server的端口不能随意改变，众所周知且不能随意改变的 2. 同一家公司的port号需要统一规范化

        // 2.将服务器的地址信息进行存放入专门的结构体
        struct sockaddr_in server;
        // 清零
        bzero(&server, sizeof(server));
        // 将服务器的IPv4点十进制IP的字符串转换为IN_ADDR结构的二进制IP。
        server.sin_addr.s_addr = inet_addr(serverip);
        // 使用的是IPv4
        server.sin_family = AF_INET;
        // 将端口号进行转换成网络序列并存入
        server.sin_port = htons(serverport);

        // 模拟云服务器命令请求，显示执行命令后的结果
        // 3.开始和服务器通信，将输入的命令行传给服务器，服务器经过处理再返回来
        while (1)
        {
            // 什么时候bind的？在我们首次系统调用发送数据的时候，OS会在底层随机选择clientport+自己的IP,1. bind 2. 构建发送的数据报文
            // cout << "please input message:";
            cout << "[轩情的服务器]#";

            string message;
            // 获取一行的命令
            getline(cin, message);

            // int sendto (socket s,const void * msg, int len, unsigned int flags, const struct sockaddr * to, int tolen ) ;
            // 将buff中的内容发送给sockaddr中指向的对应IP的port端口进程
            sendto(_socket, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));

            // 接受处理好的信息
            char buff[2048];
            // 用来存储返回数据的服务器的信息
            struct sockaddr_in tmp;
            socklen_t len = sizeof(tmp);
            // ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
            ssize_t n = recvfrom(_socket, buff, sizeof(buff) - 1, 0, (struct sockaddr *)&tmp, &len);
            // 保证错误的命令不会被显示出来，当输入错误命令时，服务器中处理完的数据中是空字符串，n为0
            if (n > 0)
            {
                //debug
                //cout << n << endl;
                
                // 显示命令结果
                buff[n] = '\0';
                cout << buff;
            }
        }
    }

private:
    int _socket;
};